package com.chukun.gateway.core.filter.impl;

import com.chukun.gateway.common.code.ResponseCode;
import com.chukun.gateway.common.exception.ConnectException;
import com.chukun.gateway.common.exception.ResponseException;
import com.chukun.gateway.common.rule.Rule;
import com.chukun.gateway.core.config.GatewayConfigLoader;
import com.chukun.gateway.core.context.GatewayContext;
import com.chukun.gateway.core.filter.Filter;
import com.chukun.gateway.core.filter.annotation.FilterDefinition;
import com.chukun.gateway.core.helper.AsyncHttpHelper;
import com.chukun.gateway.core.helper.ResponseHelper;
import com.chukun.gateway.core.response.GatewayResponse;
import com.netflix.hystrix.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.asynchttpclient.Request;
import org.asynchttpclient.Response;

import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;

import static com.chukun.gateway.common.constants.GlobalFilterConst.*;

/**
 * @author chukun
 * @version 1.0.0
 * @description 路由过滤器
 * @createTime 2023/12/3 22:39
 */
@Slf4j
@FilterDefinition(id=ROUTER_FILTER_ID, name = ROUTER_FILTER_NAME, order = ROUTER_FILTER_ORDER)
public class RouterFilter implements Filter {

    @Override
    public void doFilter(GatewayContext ctx) throws Exception {

        Optional<Rule.HystrixConfig> hystrixConfig = getHystrixConfig(ctx);
        if(hystrixConfig.isPresent()){
            routeWithHystrix(ctx,hystrixConfig.get());
        }else{
            route(ctx, null);
        }
    }

    private static   Optional<Rule.HystrixConfig> getHystrixConfig(GatewayContext gatewayContext){
        Rule rule = gatewayContext.getRule();
        return rule.getHystrixConfigs().stream()
                .filter(c-> StringUtils.equals(c.getPath(), gatewayContext.getRequest().getPath()))
                .findFirst();
    }

    private void routeWithHystrix(GatewayContext ctx, Rule.HystrixConfig hystrixConfig) {
        HystrixCommand.Setter setter = HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey
                        .Factory
                        .asKey(ctx.getUniqueId()))
                .andCommandKey(HystrixCommandKey.Factory
                        .asKey(ctx.getRequest().getPath()))
                //线程池大小
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
                        .withCoreSize(hystrixConfig.getThreadCoreSize()))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        //线程池
                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                        //超时时间
                        .withExecutionTimeoutInMilliseconds(hystrixConfig.getTimeoutInMilliseconds())
                        .withExecutionIsolationThreadInterruptOnTimeout(true)
                        .withExecutionTimeoutEnabled(true));

        new HystrixCommand<>(setter) {

            @Override
            protected Object run() throws Exception {
                route(ctx, hystrixConfig).get();
                return null;
            }

            @Override
            protected Object getFallback() {
                ctx.setResponse(hystrixConfig);
                ctx.written();
                return null;
            }
        }.execute();
    }

    private CompletableFuture<Response> route(GatewayContext ctx, Rule.HystrixConfig hystrixConfig) {
        Request request = ctx.getRequest().build();
        CompletableFuture<Response> future = AsyncHttpHelper.getInstance().executeRequest(request);

        boolean whenComplete = GatewayConfigLoader.getConfig().isWhenComplete();

        if (whenComplete) {
            future.whenComplete((response, throwable) -> {
                complete(request, response, throwable, ctx, hystrixConfig);
            });
        } else {
            future.whenCompleteAsync((response, throwable) -> {
                complete(request, response, throwable, ctx, hystrixConfig);
            });
        }
        return future;
    }

    private void complete(Request request, Response response, Throwable throwable, GatewayContext gatewayContext, Rule.HystrixConfig hystrixConfig) {
        gatewayContext.releaseRequest();

        Rule rule = gatewayContext.getRule();
        int currentRetryTimes = gatewayContext.getCurrentRetryTimes();
        int confRetryTimes = rule.getRetryConfig().getTimes();
        if ((throwable instanceof TimeoutException || throwable instanceof IOException) && currentRetryTimes <= confRetryTimes && hystrixConfig == null) {
            doRetry(gatewayContext,currentRetryTimes);
            return;
        }
        try {
            if (Objects.nonNull(throwable)) {
                String url = request.getUrl();
                if (throwable instanceof TimeoutException) {
                    log.warn("complete time out {}", url);
                    gatewayContext.setThrowable(new ResponseException(ResponseCode.REQUEST_TIMEOUT));
                    gatewayContext.setResponse(GatewayResponse.buildGatewayResponse(ResponseCode.REQUEST_TIMEOUT));
                } else {
                    gatewayContext.setThrowable(new ConnectException(throwable, gatewayContext.getUniqueId(), url, ResponseCode.HTTP_RESPONSE_ERROR));
                    gatewayContext.setResponse(GatewayResponse.buildGatewayResponse(ResponseCode.HTTP_RESPONSE_ERROR));
                }
            } else {
                gatewayContext.setResponse(GatewayResponse.buildGatewayResponse(response));
            }
        } catch (Throwable t) {
            gatewayContext.setThrowable(new ResponseException(ResponseCode.INTERNAL_ERROR));
            gatewayContext.setResponse(GatewayResponse.buildGatewayResponse(ResponseCode.INTERNAL_ERROR));
            log.error("complete error", t);
        } finally {
            gatewayContext.written();
            ResponseHelper.writeResponse(gatewayContext);
        }
    }

    private void doRetry(GatewayContext gatewayContext,int retryTimes){
        System.out.println("当前重试次数为"+retryTimes);
        gatewayContext.setCurrentRetryTimes(retryTimes + 1);
        try {
            doFilter(gatewayContext);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
